package net.cyndeline.rldungeon.grammar.production

import scalax.collection.GraphEdge.UnDiEdge
import scalax.collection.immutable.Graph
import net.cyndeline.rlgraph.isomorphism.{IsomorphicMapping, NegativeCondition, ElementEquivalence}
import net.cyndeline.rldungeon.grammar.util.{MorphismFactory, Morphism}
import scala.reflect.runtime.universe._
import scala.reflect.ClassTag
import scala.util.Random
import net.cyndeline.rldungeon.dgs.graph.{DGSFunctionality, Room}
import scalax.collection.GraphPredef.OuterEdge

/**
 * Represents a modification performed on a graph, done by first matching the graph against a pattern to see
 * if a matching topology exist, then by applying a component production to that sub-graph.
 *
 * @param pattern A graph whose vertices and edges matches the sought-after topology to apply the production to. Edges
 *                will only be taken into account in the manner they connect vertices, not by checking edge-specific
 *                data.
 * @param matcher Compares vertices in the pattern with vertices in the input graph to see if the graph contains a
 *                sub graph whose topology matches the pattern.
 * @param negativeCondition Specifies properties that the supplied graph is not allowed to have even though its
 *                          topology matched the pattern.
 * @param isomorphismMapper Examines every supplied graph for sub-structures that matches the pattern, and returns
 *                          one of them.
 * @param morphismFactory Factory responsible for building morphisms based on isomorphic mappings.
 * @tparam VType Type of vertices used in graph to apply production on.
 * @tparam EType Type of edges used in graph to apply production on.
 */
abstract class Production[VType <: Room[VType] : TypeTag, EType[X] <: UnDiEdge[X] with DGSFunctionality[X, EType] with OuterEdge[X, EType]: ({type l[M[_]] = TypeTag[M[VType]]})#l : ({type l[M[_]] = ClassTag[M[VType]]})#l](
  pattern: Graph[VType, EType],
  matcher: ElementEquivalence[VType, EType],
  negativeCondition: Option[NegativeCondition[VType, EType]],
  random: Random,
  isomorphismMapper: IsomorphicMapping,
  morphismFactory: MorphismFactory) extends GraphProduction[VType, EType] {

  /**
   * Modifies a graph.
   *
   * @param graph Graph to modify.
   * @return the graph resulting from modification if one was performed, otherwise None.
   */
  def apply(graph: Graph[VType, EType]): Option[Graph[VType, EType]]

  /**
   * Checks if a graph matches the production pattern, and returns a morphism.
   * @param graph Graph to match against pattern.
   * @return a morphism containing the pattern vertices, mapped against the vertices in the graph if a match was found.
   *         Otherwise None.
   */
  final def getMatch(graph: Graph[VType, EType]): Option[Morphism[VType]] = {
    val isomorphismInspector: IsomorphicMapping = isomorphismMapper

    val mapping: Option[Map[VType, VType]] = isomorphismInspector.randomIsomorphicMapping[VType, EType](pattern, graph, matcher, random, negativeCondition)

    if (!mapping.isDefined)
      None
    else
      Some(morphismFactory.build(mapping.get))
  }

}
